package ai.timefold.solver.core.impl.heuristic.selector.move.factory;

import java.util.Iterator;
import java.util.Random;

import ai.timefold.solver.core.api.domain.solution.PlanningSolution;
import ai.timefold.solver.core.api.score.director.ScoreDirector;
import ai.timefold.solver.core.config.heuristic.selector.common.SelectionCacheType;
import ai.timefold.solver.core.config.solver.EnvironmentMode;
import ai.timefold.solver.core.impl.heuristic.move.Move;

/**
 * An interface to generate an {@link Iterator} of custom {@link Move}s.
 * <p>
 * For a more simple version, see {@link MoveListFactory}.
 *
 * @param <Solution_> the solution type, the class with the {@link PlanningSolution} annotation
 */
public interface MoveIteratorFactory<Solution_, Move_ extends Move<Solution_>> {

    static <Solution_> Iterator<ai.timefold.solver.core.preview.api.move.Move<Solution_>>
            adaptIterator(Iterator<Move<Solution_>> moveIterator) {
        return new LegacyIteratorAdapter<>(moveIterator);
    }

    /**
     * Called when the phase (for example Local Search) starts.
     *
     * @param scoreDirector never null
     */
    default void phaseStarted(ScoreDirector<Solution_> scoreDirector) {
    }

    /**
     * Called when the phase (for example Local Search) ends,
     * to clean up anything cached since {@link #phaseStarted(ScoreDirector)}.
     *
     * @param scoreDirector never null
     */
    default void phaseEnded(ScoreDirector<Solution_> scoreDirector) {
    }

    /**
     * @param scoreDirector never null, the {@link ScoreDirector}
     *        which has the {@link ScoreDirector#getWorkingSolution()} of which the {@link Move}s need to be generated
     * @return the approximate number of elements generated by {@link #createOriginalMoveIterator(ScoreDirector)}
     * @throws UnsupportedOperationException if not supported
     */
    long getSize(ScoreDirector<Solution_> scoreDirector);

    /**
     * When it is called depends on the configured {@link SelectionCacheType}.
     *
     * @param scoreDirector never null, the {@link ScoreDirector}
     *        which has the {@link ScoreDirector#getWorkingSolution()} of which the {@link Move}s need to be generated
     * @return never null, an {@link Iterator} that will end sooner or later
     * @throws UnsupportedOperationException if only {@link #createRandomMoveIterator(ScoreDirector, Random)} is
     *         supported
     */
    Iterator<Move_> createOriginalMoveIterator(ScoreDirector<Solution_> scoreDirector);

    /**
     * When it is called depends on the configured {@link SelectionCacheType}.
     *
     * @param scoreDirector never null, the {@link ScoreDirector}
     *        which has the {@link ScoreDirector#getWorkingSolution()} of which the {@link Move}s need to be generated
     * @param workingRandom never null, the {@link Random} to use when any random number is needed,
     *        so {@link EnvironmentMode#PHASE_ASSERT} works correctly
     * @return never null, an {@link Iterator} that is allowed (or even presumed) to be never ending
     * @throws UnsupportedOperationException if only {@link #createOriginalMoveIterator(ScoreDirector)} is supported
     */
    Iterator<Move_> createRandomMoveIterator(ScoreDirector<Solution_> scoreDirector, Random workingRandom);

}
